package com.feeyo.groundservice.ws.utils

import android.util.Log
import com.blankj.utilcode.util.GsonUtils
import com.feeyo.groundservice.ws.common.CONNECTION_LOST_TIMEOUT
import com.feeyo.groundservice.ws.common.CONNECT_TIMEOUT
import com.feeyo.groundservice.ws.common.MAX_RECONNECT_TIMES
import com.feeyo.groundservice.ws.common.MAX_RECONNECT_TIMES_IN_ONE_MINUTE
import com.feeyo.groundservice.ws.common.READ_TIMEOUT
import com.feeyo.groundservice.ws.common.WRITE_TIMEOUT
import com.feeyo.groundservice.ws.common.WebSocketSetting
import com.feeyo.groundservice.ws.common.WebSocketStatus
import com.feeyo.groundservice.ws.model.InnerMsgType
import com.feeyo.groundservice.ws.model.LoginMsg
import com.feeyo.groundservice.ws.monitor.WebSocketMsgReceiveListener
import com.feeyo.groundservice.ws.monitor.WebSocketStatusListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.*
import org.json.JSONObject
import java.net.InetSocketAddress
import java.net.Proxy
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

/**
 *作者：QLH on 2019-06-12
 *描述：im服务工具类
 * 功能：管理websocket 配置 服务启动 关闭 数据发送 数据接收
 * 可以设置是否进行重连，配置心跳时间
 */
class WebSocketManager {

    private val TAG = "wsm"
    private var mSetting: WebSocketSetting? = null//配置
    private var mWebSocket: WebSocket? = null
    private var mOkHttpClient: OkHttpClient? = null
    private var mRequest: Request? = null
    private var heartJob: Job? = null//心跳

    private var wsStatusListener: WebSocketStatusListener? = null//连接状态监听器
    private var wsMsgReceiveListener: WebSocketMsgReceiveListener? = null//接收监听器
    private var mCurrentStatus = WebSocketStatus.DISCONNECTED     //websocket连接状态
    private var reconnectCount = 0   //重连次数
    private var isManualClose = false         //是否为手动关闭websocket连接
    private var isNeedReconnect = false          //是否需要断线自动重连
    private val RECONNECT_INTERVAL = (1 * 1000).toLong()   //重连自增步长
    private val RECONNECT_MAX_TIME = (12 * 1000).toLong()   //最大重连间隔
    private var netWorkStateJob: Job? = null //重连
    private var delay = 0L //重连间隔
    private var loginMsg: String? = null//登录消息

    //心跳消息
    private val pingMsg = hashMapOf<String, String>()

    private var reConnectNumInOneMinute = 0//一分钟之内重连次数，如果超过一定数量就认为是异常连接，停止重连
    private var startReConnectTime: Long = 0L//开始重连时间
    private var finishReconnect: Boolean = false//是否完成重连
    private val noNeedReConnectFlag = arrayListOf<String>()//无需重连的错误命令

    companion object {
        /**记录每一个连接对象,<key,value>=<标签，对象>**/
        private val wsManagerMap = HashMap<String, WebSocketManager?>()

        /**存储WebSocketManager对象，方便每不同模块定制不同的WebSocketManager对象**/
        fun setWsm(key: String, wsm: WebSocketManager?) {
            //先停止历史的连接
            wsManagerMap[key]?.stopConnectService()
            wsManagerMap[key] = wsm
        }

        /**根据key获取WebSocketManager对象**/
        fun getWsm(key: String) = wsManagerMap[key]

        /**根据key移除存储的WebSocketManager对象**/
        fun removeWsm(key: String) {
            //先停止历史的连接
            wsManagerMap[key]?.stopConnectService()
            wsManagerMap.remove(key)
        }
    }

    /**设置websocket参数**/
    fun setWebSocketSetting(setting: WebSocketSetting): WebSocketManager {
        mSetting = setting
        init()
        return this
    }

    /**获取当前websocket配置数据**/
    fun getWebSocketSetting() = mSetting

    /**获取当前连接状态**/
    fun getWebSocketStatus() = mCurrentStatus

    /**重连是否完成**/
    fun getFinishReconnect() = finishReconnect

    /**是否主动关闭**/
    fun getManualClose(): Boolean = isManualClose

    /**设置websocket默认参数**/
    fun setWebSocketSetting(
        url: String = "",
        host: String = "",
        port: Int = 0,
        pingIntervalTime: Long = 0,
        notSSL: Boolean = false,
    ): WebSocketManager {
        val setting = WebSocketSetting()
        setting.setConnectUrl(url)
        setting.setConnectionLostTimeout(30)
        setting.setReadTimeout(5)
        setting.setWriteTimeout(5)
        setting.setConnectTimeout(5)
        setting.setHost(host)
        setting.setPort(port)
        setting.setPingInterval(pingIntervalTime)
        setting.setNotSSL(notSSL)
        setWebSocketSetting(setting)
        return this
    }

    /**设置websocket状态监听器**/
    fun setWsStatusListener(listener: WebSocketStatusListener): WebSocketManager {
        wsStatusListener = listener
        return this
    }

    /**是否需要重连**/
    fun needReconnect(need: Boolean): WebSocketManager {
        isNeedReconnect = need
        return this
    }

    /**设置接收消息监听器**/
    fun setWsMsgReceiveListener(msgReceiveListener: WebSocketMsgReceiveListener): WebSocketManager {
        wsMsgReceiveListener = msgReceiveListener
        return this
    }

    /**设置登录消息体，必须设置**/
    fun setLoginMsg(uid: String): WebSocketManager {
        loginMsg = GsonUtils.toJson(LoginMsg(uid = uid))
        return this
    }

    /**自定义登录消息结构**/
    fun setCustomLoginMsg(msg: String): WebSocketManager {
        loginMsg = msg
        return this
    }

    /**自定义心跳消息结构：其他附带信息**/
    //增加websocket对象地址
    fun setCustomPingExtraMsg(extraMsg: HashMap<String, String>) {
        pingMsg.apply {
            this["type"] = InnerMsgType.Ping
            for ((k, v) in extraMsg)
                this[k] = v
        }
    }

    /**无需重连的错误**/
    fun setNoNeedReConnectList(commandList: List<String>) {
        noNeedReConnectFlag.apply {
            clear()
            addAll(commandList)
        }
    }

    /**启动服务**/
    @Synchronized
    fun startConnectService() {
        FyLog.d(
            TAG,
            "socket url: ${canConnect()} and running: ${mCurrentStatus == WebSocketStatus.CONNECTED}"
        )

        FyLog.d(TAG, "socket status:$mCurrentStatus")
        if (canConnect() && (mCurrentStatus == WebSocketStatus.DISCONNECTED || mCurrentStatus == WebSocketStatus.RECONNECT)) {
            if (loginMsg == null) {
                FyLog.e(TAG, "请设置login消息体")
                return
            }
            isManualClose = false
            connect()
        }
    }

    /**停止服务**/
    fun stopConnectService() {
        disconnect()
    }

    /**发送消息
     *
     * 用户发送的消息 都会给自己一个发送成功的反馈 type发什么 反馈就是什么
     * 如发送结构为{"type","say"...} 成功反馈为{"type","say"...}
     * **/
    fun sendMsg(msg: Any, isReconnect: Boolean = true) {
        if (mWebSocket == null || mCurrentStatus != WebSocketStatus.CONNECTED) {
            if (isReconnect) {
                wsStatusListener?.onReconnectException("socket连接没有建立")
            }
            return
        }
        val message = when (msg) {
            is String -> msg
            is Map<*, *> -> GsonUtils.toJson(LinkedHashMap(msg))
            else -> GsonUtils.toJson(msg)
        }
        FyLog.d(TAG, "send msg:$message")
        mWebSocket?.send(message)
    }

    /**是否可以进行连接**/
    private fun canConnect() = mSetting != null && !mSetting!!.getConnectUrl().isNullOrEmpty()

    /**所有的配置完成后方可调用此函数**/
    private fun init() {
        if (canConnect()) {
            mOkHttpClient = OkHttpClient.Builder()
                .readTimeout(mSetting?.getReadTimeout() ?: READ_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(mSetting?.getWriteTimeout() ?: WRITE_TIMEOUT, TimeUnit.SECONDS)
                .connectTimeout(
                    mSetting?.getConnectTimeout()
                        ?: CONNECT_TIMEOUT, TimeUnit.SECONDS
                )
                .pingInterval(mSetting?.getPingInterval() ?: 0L, TimeUnit.SECONDS)
                .apply {
                    if (mSetting!!.getHost().isEmpty()) {
                        Log.e("socket_host", "--")
                    } else if (mSetting!!.getPort() == 0) {
                        //绑host
                        addInterceptor(object : Interceptor {
                            override fun intercept(chain: Interceptor.Chain): Response {
                                val originalRequest = chain.request()
                                val host = mSetting!!.getHost()
                                val authorised = originalRequest.newBuilder()
                                    .header("host", host)
                                    .build()
                                Log.e("socket_host", host)
                                return chain.proceed(authorised)
                            }
                        })
                    } else {//走代理了
                        val host = mSetting!!.getHost()
                        val port = mSetting!!.getPort()
                        FyLog.e("Socks模式", "$host----$port")
                        val proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port))
                        proxy(proxy)
                    }
                    //是否需要忽略证书
                    if (mSetting!!.isNotSSL()) {
                        sslSocketFactory(
                            SSLSocketClient.getSSLSocketFactory(),
                            SSLSocketClient.getTrustAllCert()
                        )//忽略htts的证书验证
                        hostnameVerifier { hostname, session -> true }
                    }
                }

                .build()

            mSetting?.getConnectUrl()?.let {
                mRequest = Request.Builder().url(it).build()
            }
        }
    }

    private fun getSSLFactory(): SSLSocketFactory {
        //证书忽略添加下面代码（1）打开即可
//         Create a trust manager that does not validate certificate chains
        val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate> {
                return arrayOf()
            }

            @Throws(CertificateException::class)
            override fun checkClientTrusted(
                chain: Array<java.security.cert.X509Certificate>,
                authType: String
            ) {
            }

            @Throws(CertificateException::class)
            override fun checkServerTrusted(
                chain: Array<java.security.cert.X509Certificate>,
                authType: String
            ) {
            }
        })

        val sslContext = SSLContext.getInstance("SSL")
        sslContext.init(null, trustAllCerts, java.security.SecureRandom())
        // Create an ssl socket factory with our all-trusting manager
        return sslContext.socketFactory
    }

    /**连接，使用全局OKHttpClient和request**/
    @Synchronized
    private fun connect() {
        FyLog.d(TAG, "connect...")
        if (mWebSocket == null && canConnect())
            mOkHttpClient?.let { client ->
                mCurrentStatus = WebSocketStatus.CONNECTING
                mRequest?.let {
                    mWebSocket = client.newWebSocket(it, getSocketListener()).apply {
                    }
                }
                //it.dispatcher().executorService().shutdown()//清除并关闭线程池
            } else {
            FyLog.d(TAG, "socket已存在")
            disconnect()
        }
    }

    /**断开连接**/
    private fun disconnect() {
        netWorkStateJob?.cancel()
        heartJob?.cancel()
        reconnectCount = 0
        delay = 0L
        reConnectNumInOneMinute = 0
        startReConnectTime = 0L
        isManualClose = true
        finishReconnect = false
        pingMsg.clear()
        loginMsg = null
        noNeedReConnectFlag.clear()
        //关闭socket
        closeSocket()
    }

    private fun closeSocket() {
        try {
            mWebSocket?.close(1000, "client close")
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            mWebSocket = null
            mCurrentStatus = WebSocketStatus.DISCONNECTED
        }
    }

    /**清除并关闭线程池**/
    fun shutDownPool() {
        mOkHttpClient?.let {
            if (!it.dispatcher.executorService.isShutdown) {
                it.dispatcher.executorService.shutdown()
            }
        }
    }

    /**获取socket 监听器[监听在后台线程 注意]**/
    private fun getSocketListener() = object : WebSocketListener() {
        override fun onOpen(webSocket: WebSocket, response: Response) {
            super.onOpen(webSocket, response)
            FyLog.d(TAG, "connect success **")
            mCurrentStatus = WebSocketStatus.CONNECTED
            wsStatusListener?.onOpen(response)
            if (reconnectCount > 0) {
                wsStatusListener?.onReconnectSuccess()
            }
            reconnectCount = 0
            netWorkStateJob?.cancel()
            delay = 0L
            finishReconnect = true
            //登录IM
            loginMsg?.let { sendMsg(it, false) }
            //发一次心跳包
            sendMsg(pingMsg.apply {
                this["wsId"] = mWebSocket.toString()
                this["time"] = System.currentTimeMillis().toString()
            }, false)
        }

        override fun onMessage(webSocket: WebSocket, text: String) {
            super.onMessage(webSocket, text)
            FyLog.d(TAG, "receive msg:$text")
            onStringMsg(text)
        }

        override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
            super.onFailure(webSocket, t, response)
            closeSocket()
            //记录单位时间内重连次数
            reConnectNumInOneMinute++
            if (startReConnectTime == 0L) {
                startReConnectTime = System.currentTimeMillis()
            }
            if (System.currentTimeMillis() - startReConnectTime < 60 * 1000 && reConnectNumInOneMinute > MAX_RECONNECT_TIMES_IN_ONE_MINUTE) {
                FyLog.e(TAG, "短时间内重连次数异常，请检查")
                reConnectNumInOneMinute = 0
                startReConnectTime = 0L
                finishReconnect = true
                return
            }
            wsStatusListener?.onFailure(t, response)
            if (reconnectCount < (mSetting?.getMaxReconnectTimes() ?: MAX_RECONNECT_TIMES)) {
                tryReconnect()
            } else {//到达最大重连次数后
                finishReconnect = true
                mCurrentStatus = WebSocketStatus.DISCONNECTED
                reconnectCount = 0
                FyLog.e(TAG, "到达最大重连次数:${mSetting?.getMaxReconnectTimes()}")
            }
        }

        override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
            super.onClosing(webSocket, code, reason)
            FyLog.d(TAG, "onClosing:$reason")
            mWebSocket = null
            mCurrentStatus = WebSocketStatus.DISCONNECTED
            wsStatusListener?.onClosing(reason)
        }

        override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
            super.onClosed(webSocket, code, reason)
            FyLog.d(TAG, "onClosed:$reason")
            mWebSocket = null
            mCurrentStatus = WebSocketStatus.DISCONNECTED
            wsStatusListener?.onClosed(reason)
        }
    }

    /**
     * 接收到string类型的消息
     *
     * @param msg
     */
    private fun onStringMsg(msg: String) {
        if (msg.isEmpty()) return
        val msgJson = toJsonObject(msg)

        val type = msgJson?.getString("type") ?: ""
        if (type.isEmpty()) {
            return
        }

        //消息分类解析
        when (type) {

            InnerMsgType.Ping, InnerMsgType.Pong -> {//心跳  45秒内回复{"type": "ping"}
                heartJob?.cancel()
                heartJob = CoroutineScope(Dispatchers.IO).launch {
                    val delayTime = mSetting?.getConnectionLostTimeout() ?: CONNECTION_LOST_TIMEOUT
                    delay(delayTime * 1000)
                    sendMsg(pingMsg.apply {
                        this["wsId"] = mWebSocket.toString()
                        this["time"] = System.currentTimeMillis().toString()
                    }, false)
                }
            }

            else -> {
                wsMsgReceiveListener ?: FyLog.e(TAG, "没有设置消息回调监听器")
                wsMsgReceiveListener?.receiveMsg(msg)
                //拦截事先定义的不需要重连的错误
                if (noNeedReConnectFlag.isNotEmpty() && type in noNeedReConnectFlag) {
                    isNeedReconnect = false
                    FyLog.e(TAG, "$type：无需重连")
                }

                reConnectNumInOneMinute = 0
                startReConnectTime = 0L
            }
        }
    }

    @Synchronized
    private fun tryReconnect() {

        FyLog.d(TAG, "isNeedReconnect：$isNeedReconnect---isManualClose：$isManualClose")
        if (!isNeedReconnect || isManualClose) return

        FyLog.d(TAG, "reconnectCount=$reconnectCount,delay=$delay")
        finishReconnect = false

        netWorkStateJob?.cancel()
        netWorkStateJob = CoroutineScope(Dispatchers.IO).launch {
            delay(delay)

            mCurrentStatus = WebSocketStatus.RECONNECT
            reconnectCount++
            delay = reconnectCount * RECONNECT_INTERVAL
            delay = if (delay > RECONNECT_MAX_TIME) RECONNECT_MAX_TIME else delay
            if (wsStatusListener != null) {
                wsStatusListener?.onReconnect()
            }
        }
    }

    /**解析json 包括异常捕获**/
    private fun toJsonObject(string: String?): JSONObject? {
        if (string.isNullOrEmpty()) return null
        try {
            val element = JSONObject(string)
            if (null != element) return element
        } catch (e: Exception) {
            wsStatusListener?.onFailure(Throwable("json parse error:$string"), null)
            e.printStackTrace()
        }
        return null
    }
}

